﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Mime
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>Represents a collection of header fields that are directly accessible by name.</summary>
    /// <remarks>The header fields are arranged in <see cref="IGrouping{T, U}"/> objects, where the
    /// <see cref="IGrouping{T, U}.Key"/> property represents the header field name. There's exactly one grouping for
    /// each case-insensitively distinct header field name.</remarks>
    /// <threadsafety static="true" instance="false"/>
    public sealed class HeaderFieldCollection : ILookup<string, HeaderField>
    {
        private readonly Dictionary<string, HeaderFieldGrouping> groupings =
            new Dictionary<string, HeaderFieldGrouping>(16, StringComparer.OrdinalIgnoreCase);

        private int count;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Adds a header field.</summary>
        /// <exception cref="ArgumentNullException"><paramref name="headerField"/> equals <c>null</c>.</exception>
        public void Add(HeaderField headerField)
        {
            if (headerField == null)
            {
                throw new ArgumentNullException("headerField");
            }

            this.GetGrouping(headerField.Name).Add(headerField);
            ++this.count;
        }

        /// <summary>Removes the header fields with the given name.</summary>
        /// <returns>The number of header fields removed.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="headerFieldName"/> equals <c>null</c>.</exception>
        public int Remove(string headerFieldName)
        {
            if (headerFieldName == null)
            {
                throw new ArgumentNullException("headerFieldName");
            }

            HeaderFieldGrouping grouping;

            if (this.groupings.TryGetValue(headerFieldName, out grouping))
            {
                this.groupings.Remove(headerFieldName);
                this.count -= grouping.Count;
                return grouping.Count;
            }

            return 0;
        }

        /// <summary>Removes the given header field from the collection.</summary>
        /// <returns><c>true</c> if the element is successfully removed; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="headerField"/> equals <c>null</c>.</exception>
        public bool Remove(HeaderField headerField)
        {
            if (headerField == null)
            {
                throw new ArgumentNullException("headerField");
            }

            HeaderFieldGrouping grouping;
            bool removed = this.groupings.TryGetValue(headerField.Name, out grouping) && grouping.Remove(headerField);

            if (removed)
            {
                --this.count;
            }

            return removed;
        }

        /// <summary>Removes all header fields from the collection.</summary>
        public void Clear()
        {
            this.groupings.Clear();
            this.count = 0;
        }

        /// <summary>See <see cref="ILookup{T, U}.Contains"/>.</summary>
        /// <remarks>Determines if the collection contains at least one header field, the name of which
        /// case-insensitively matches <paramref name="key"/>.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> equals <c>null</c>.</exception>
        public bool Contains(string key)
        {
            HeaderFieldGrouping grouping;
            return this.groupings.TryGetValue(key, out grouping) && (grouping.Count > 0);
        }

        /// <summary>See <see cref="ILookup{T, U}.Count"/>.</summary>
        public int Count
        {
            get { return this.count; }
        }

        /// <summary>See <see cref="ILookup{T, U}.this"/>.</summary>
        /// <remarks>Gets the collection of header fields, the names of which case-insensitively match
        /// <paramref name="key"/>.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="key"/> equals <c>null</c>.</exception>
        public IEnumerable<HeaderField> this[string key]
        {
            get { return this.GetGrouping(key); }
        }

        /// <summary>See <see cref="IEnumerable{T}.GetEnumerator"/>.</summary>
        /// <remarks>Returns an enumerator that iterates through the header fields grouped by name
        /// (<see cref="IGrouping{T, U}.Key"/> contains the header field name). Each returned grouping contains at least
        /// one header field.</remarks>
        public IEnumerator<IGrouping<string, HeaderField>> GetEnumerator()
        {
            foreach (var grouping in this.groupings.Values)
            {
                if (grouping.Count > 0)
                {
                    yield return grouping;
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal HeaderFieldCollection()
        {
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IEnumerable.GetEnumerator"/>.</summary>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        private HeaderFieldGrouping GetGrouping(string key)
        {
            HeaderFieldGrouping grouping;

            if (!this.groupings.TryGetValue(key, out grouping))
            {
                grouping = new HeaderFieldGrouping(key);
                this.groupings.Add(key, grouping);
            }

            return grouping;
        }
    }
}
